package com.supermap.crypto;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @Auther EdgarLQS
 * @Date 2020-11-15 10:29
 */
public class CryptoUtils {

    private RSAPublicKey rsaPublicKey = null;
    private RSAPrivateKey rsaPrivateKey = null;
    private DSAPublicKey dsaPublicKey = null;
    private DSAPrivateKey dsaPrivateKey = null;
    private ECPublicKey ecPublicKey = null;
    private ECPrivateKey ecPrivateKey = null;

    public CryptoUtils() {

    }

    public RSAPublicKey getRsaPublicKey() {
        return rsaPublicKey;
    }

    public RSAPrivateKey getRsaPrivateKey() {
        return rsaPrivateKey;
    }

    public DSAPublicKey getDsaPublicKey() {
        return dsaPublicKey;
    }

    public DSAPrivateKey getDsaPrivateKey() {
        return dsaPrivateKey;
    }

    public ECPublicKey getEcPublicKey() {
        return ecPublicKey;
    }

    public ECPrivateKey getEcPrivateKey() {
        return ecPrivateKey;
    }

    /**
     * 根据加密算法和加密长度生成证书秘钥
     *
     * @param cryptoType
     * @param cryptoLength
     */
    public void generatekeys(CryproType cryptoType, int cryptoLength) {
        try {
            if (cryptoType == CryproType.RSA) {
                // 1.初始化密钥
                KeyPairGenerator keyPairGenerator = KeyPairGenerator
                        .getInstance("RSA");
                //设置KEY的长度
                keyPairGenerator.initialize(cryptoLength);
                KeyPair keyPair = keyPairGenerator.generateKeyPair();

                rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
                rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            } else {
                System.out.println("您的加密算法名称有误，请检查后再输入");
                System.exit(0);
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据加密算法和加密长度生成证书秘钥
     *
     * @param cryptoName
     * @param cryptoLength
     */
    public static KeyPair generateKeyPair(String cryptoName, int cryptoLength) {
        try {
            // 1.初始化密钥
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(cryptoName);
            //设置KEY的长度
            keyPairGenerator.initialize(cryptoLength);
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 签名过程 RSA
     *
     * @param cryptoName    加密名称
     * @param rsaPrivateKey 私钥
     * @param signatureInfo 签名信息
     * @return
     */
    public byte[] signatureByRSA(String cryptoName, RSAPrivateKey rsaPrivateKey, String signatureInfo) {

        byte[] result = null;
        try {
            //用私钥进行签名
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
                    rsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个privateKey
            PrivateKey privateKey = keyFactory
                    .generatePrivate(pkcs8EncodedKeySpec);
            //声明签名的对象
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initSign(privateKey);
            signature.update(signatureInfo.getBytes());
            //进行签名
            result = signature.sign();
        } catch (Exception e) {
            e.getMessage();
        }
        return result;
    }

    /**
     * 签名过程 DSA
     *
     * @param cryptoName    加密名称
     * @param dsaPrivateKey 私钥
     * @param signatureInfo 签名信息
     * @return
     */
    public byte[] signatureByDSA(String cryptoName, DSAPrivateKey dsaPrivateKey, String signatureInfo) {

        byte[] result = null;
        try {
            //用私钥进行签名
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
                    dsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个privateKey
            PrivateKey privateKey = keyFactory
                    .generatePrivate(pkcs8EncodedKeySpec);
            //声明签名的对象
            Signature signature = Signature.getInstance("SHA1withDSA");
            signature.initSign(privateKey);
            signature.update(signatureInfo.getBytes());
            //进行签名
            result = signature.sign();
        } catch (Exception e) {
            e.getMessage();
        }
        return result;
    }

    /**
     * 签名过程 ECDSA
     *
     * @param cryptoName    加密名称
     * @param ecPrivateKey  私钥
     * @param signatureInfo 签名信息
     * @return
     */
    public byte[] signatureByECDSA(String cryptoName, ECPrivateKey ecPrivateKey, String signatureInfo) {

        byte[] result = null;
        try {
            //用私钥进行签名
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
                    ecPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个privateKey
            PrivateKey privateKey = keyFactory
                    .generatePrivate(pkcs8EncodedKeySpec);
            //声明签名的对象
            Signature signature = Signature.getInstance("SHA1withECDSA");
            signature.initSign(privateKey);
            signature.update(signatureInfo.getBytes());
            //进行签名
            result = signature.sign();
        } catch (Exception e) {
            e.getMessage();
        }
        return result;
    }

    /**
     * 验签 RSA
     *
     * @param cryptoName
     * @param rsaPublicKey
     * @param signatureResult
     * @param cryptoInfo
     * @return
     */
    public Boolean verifySignatureByRSA(String cryptoName, RSAPublicKey rsaPublicKey, byte[] signatureResult, String cryptoInfo) {

        boolean bool = false;
        try {
            // 3.验证签名
            //用公钥进行验证签名
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
                    rsaPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个publicKey
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //声明签名对象
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(publicKey);
            signature.update(cryptoInfo.getBytes());
            //验证签名
            bool = signature.verify(signatureResult);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return bool;
    }


    /**
     * 验签 DSA
     *
     * @param cryptoName
     * @param dsaPublicKey
     * @param signatureResult
     * @param cryptoInfo
     * @return
     */
    public Boolean verifySignatureByDSA(String cryptoName, DSAPublicKey dsaPublicKey, byte[] signatureResult, String cryptoInfo) {

        boolean bool = false;
        try {
            // 3.验证签名
            //用公钥进行验证签名
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
                    dsaPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个publicKey
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //声明签名对象
            Signature signature = Signature.getInstance("SHA1withDSA");
            signature.initVerify(publicKey);
            signature.update(cryptoInfo.getBytes());
            //验证签名
            bool = signature.verify(signatureResult);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return bool;
    }


    /**
     * 验签 ECDSA
     *
     * @param cryptoName
     * @param ecpPublicKey
     * @param signatureResult
     * @param cryptoInfo
     * @return
     */
    public Boolean verifySignatureByECDSA(String cryptoName, ECPublicKey ecpPublicKey, byte[] signatureResult, String cryptoInfo) {

        boolean bool = false;
        try {
            // 3.验证签名
            //用公钥进行验证签名
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
                    ecpPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(cryptoName);
            //构造一个publicKey
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //声明签名对象
            Signature signature = Signature.getInstance("SHA1withECDSA");
            signature.initVerify(publicKey);
            signature.update(cryptoInfo.getBytes());
            //验证签名
            bool = signature.verify(signatureResult);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return bool;
    }

    public static byte[] rsaEncrypt(String str, PublicKey pubKey) throws Exception {
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        byte[] bytes = cipher.doFinal(str.getBytes("UTF-8"));
        return bytes;
    }

    public static String rsaDecrypt(byte[] bytes, PrivateKey priKey) throws Exception {
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        String outStr = new String(cipher.doFinal(bytes));
        return outStr;
    }

    public String rsaPublicKeytoPem(RSAPublicKey rsaPublicKey) {
        try {
            PemObject pem = new PemObject("PUBLIC KEY", rsaPublicKey.getEncoded());
            StringWriter str = new StringWriter();
            PemWriter pemWriter = new PemWriter(str);
            pemWriter.writeObject(pem);
            pemWriter.close();
            str.close();
            return str.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String rsaPrivateKeytoPem(RSAPrivateKey rsaPrivateKey) {
        try {
            OutputEncryptor encryptor = null;
            PKCS8Generator generator = new JcaPKCS8Generator(rsaPrivateKey, encryptor);
            StringWriter stringWriter = new StringWriter();
            JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
            pemWriter.writeObject(generator);
            pemWriter.close();
            return stringWriter.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public RSAPublicKey loadRSAPublicKeyFromFile(String filename) {
        try {
            File f = new File(filename);
            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);
            byte[] keyBytes = new byte[(int) f.length()];
            dis.readFully(keyBytes);
            dis.close();
            String temp = new String(keyBytes);
            String publicKeyPEM = temp.replace("-----BEGIN PUBLIC KEY-----\n", "");
            publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
            // TODO 使用 sun.misc.BASE64Decoder 时，当 jdk 版本高于 1.8 时，由于底层不在有该包，所以会出错  计划改为 java.util.Base64 add by liqs 20210927
            // client 和 server 需要一起修改  计划为 10.3 修改
//            byte[] decoded = new BASE64Decoder().decodeBuffer(temp);
            byte[] decoded = BASE64.getDecoder(temp);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
            PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
            return (RSAPublicKey) publicKey;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public RSAPrivateKey loadRSAPrivateKeyFromFile(String filename) {
        try {
            FileReader fr = new FileReader(new File(filename));
            PEMParser pemReader = new PEMParser(fr);
            Object obj = pemReader.readObject();
            pemReader.close();
            JcaPEMKeyConverter converter = (new JcaPEMKeyConverter()).setProvider(BouncyCastleProvider.PROVIDER_NAME);
            PrivateKeyInfo obj1 = (PrivateKeyInfo) obj;
            PrivateKey privateKey = converter.getPrivateKey(obj1);
            return (RSAPrivateKey) privateKey;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void saveRSAPrivateKeyToFile(PrivateKey priKey, String filePath) {
        try {
            saveRSAStringToFile(RSAPrivToString(priKey), filePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void saveRSAPublicKeyToFile(PublicKey pubKey, String filePath) {
        try {
            saveRSAStringToFile(RSAPubToString(pubKey), filePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String RSAPrivToString(PrivateKey priKey) {
        try {
            // 获取 公钥和私钥 的 编码格式（通过该 编码格式 可以反过来 生成公钥和私钥对象）
            byte[] pubEncBytes = priKey.getEncoded();
            // 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存
//            return new BASE64Encoder().encode(pubEncBytes);
            return BASE64.getEncoder(pubEncBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String RSAPubToString(PublicKey pubKey) {
        try {
            if (pubKey == null) {
                return null;
            }
            // 获取 公钥和私钥 的 编码格式（通过该 编码格式 可以反过来 生成公钥和私钥对象）
            byte[] pubEncBytes = pubKey.getEncoded();
            // 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存
//            return new BASE64Encoder().encode(pubEncBytes);
            return BASE64.getEncoder(pubEncBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void saveRSAStringToFile(String rsaPrivString, String filePath) {
        try {
            IOUtils.writeFile(rsaPrivString, new File(filePath));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static PrivateKey getRSAPrivateKeyFromFile(String filepath) {
        try {
            // 从 私钥保存的文件 读取 私钥的base文本
            String priKeyBase64 = IOUtils.readFile(new File(filepath));
            // 把 私钥的Base64文本 转换为已编码的 私钥bytes
//            byte[] encPriKey = new BASE64Decoder().decodeBuffer(priKeyBase64);
            byte[] encPriKey = BASE64.getDecoder(priKeyBase64);
            // 创建 已编码的私钥规格
            PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(encPriKey);
            // 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象
            return KeyFactory.getInstance("RSA").generatePrivate(encPriKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static PrivateKey getRSAPrivateKeyFromString(String rsapriStr) {
        try {
            // 把 私钥的Base64文本 转换为已编码的 私钥bytes
//            byte[] encPriKey = new BASE64Decoder().decodeBuffer(rsapriStr);
            byte[] encPriKey = BASE64.getDecoder(rsapriStr);
            // 创建 已编码的私钥规格
            PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(encPriKey);
            // 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象
            return KeyFactory.getInstance("RSA").generatePrivate(encPriKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static PublicKey getRSAPublicKeyFromFile(String filepath) {
        try {
            // 从 公钥保存的文件 读取 公钥的Base64文本
            String pubKeyBase64 = IOUtils.readFile(new File(filepath));
            // 把 公钥的Base64文本 转换为已编码的 公钥bytes
//            byte[] encPubKey = new BASE64Decoder().decodeBuffer(pubKeyBase64);
            byte[] encPubKey = BASE64.getDecoder(pubKeyBase64);
            // 创建 已编码的公钥规格
            X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(encPubKey);
            // 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
            return KeyFactory.getInstance("RSA").generatePublic(encPubKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static PublicKey getRSAPublicKeyFromString(String rsaPubStr) {
        try {
            // 把 公钥的Base64文本 转换为已编码的 公钥bytes
//            byte[] encPubKey = new BASE64Decoder().decodeBuffer(rsaPubStr);
            byte[] encPubKey = BASE64.getDecoder(rsaPubStr);
            // 创建 已编码的公钥规格
            X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(encPubKey);
            // 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
            return KeyFactory.getInstance("RSA").generatePublic(encPubKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
